package net.techwatch.poi;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
 * @author wvandenberghe
 *
 */
public class PrivateBinder implements Binder {
	
	private String currentProperty;
	private Method currentMethod;
	
	private Map<String, Method> binding = new LinkedHashMap<String, Method>();
	private Map<String, SimpleDateFormat> format = new HashMap<String, SimpleDateFormat>();

	/* (non-Javadoc)
	 * @see poi.Binder#from(java.lang.Class)
	 */
	public Binder from(Class<?> clazz) {
		if(this.currentProperty == null)
			throw new IllegalStateException("Unknown binding property");
		
		for(Method method : clazz.getMethods()) {
			// in case each bean properties have to be binding
			if(currentProperty.equals("*")) {
				currentProperty = toPropertyName(method.getName());
			}
			// bind only the current property
			if(method.getName().equals(toSetterName(currentProperty))) {
				currentMethod = method;
			}
		}
		
		if(currentMethod == null) {
			throw new IllegalArgumentException("Cannot bind property to bean setter");
		}
		
		currentProperty = null;
		return this;
	}

	/* (non-Javadoc)
	 * @see poi.Binder#to(java.lang.String)
	 */
	public Binder to(String name) {
		if(this.currentMethod == null)
			throw new IllegalStateException("Unknown binding property");
		
		this.binding.put(name, currentMethod);
		currentMethod = null;
		
		return this;
	}

	/* (non-Javadoc)
	 * @see poi.Binder#format(java.lang.String)
	 */
	public Binder format(String format) {
		// get the latest property
		List<Entry<String, Method>> entries = new LinkedList<Entry<String,Method>>();
		entries.addAll(binding.entrySet());
		String property = entries.get(entries.size() - 1).getKey();
		
		// check for date type
		if(!Date.class.equals(this.getParameterType(property)))
			throw new IllegalArgumentException("Should be applied only for Date");
		
		this.format.put(property, new SimpleDateFormat(format));
		
		return this;
	}

	/* (non-Javadoc)
	 * @see poi.Binder#setProperty()
	 */
	public void addProperty(String property) {
		currentProperty = property;
	}
	
	private String toSetterName(String property) {
		return "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
	}
	
	private String toPropertyName(String setterName) {
		return setterName.substring(3, 1).toLowerCase() + setterName.substring(4);
	}

	/* (non-Javadoc)
	 * @see poi.Binder#getMethod(java.lang.String)
	 */
	public Method getMethod(String property) {
		return this.binding.get(property);
	}

	/* (non-Javadoc)
	 * @see poi.Binder#getParameterType(java.lang.String)
	 */
	public Class<?> getParameterType(String property) {
		Method method = this.getMethod(property);
		return method.getParameterTypes()[0];
	}

	/* (non-Javadoc)
	 * @see poi.Binder#bindableProperties()
	 */
	public String[] bindableProperties() {
		Set<String> keys = binding.keySet();
		String[] properties = new String[binding.size()];
		return keys.toArray(properties);
	}

	/* (non-Javadoc)
	 * @see poi.Binder#getDateFormat(java.lang.String)
	 */
	public SimpleDateFormat getDateFormat(String property) {
		return format.get(property);
	}

}
